import { AnimationMixer, Box3, BufferGeometry, Clock, Group, LoadingManager, Material, Mesh, Object3D, Vector3 } from "three";
import { FBXLoader } from "three/examples/jsm/loaders/FBXLoader";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import { OBJLoader } from "three/examples/jsm/loaders/OBJLoader";
import { TGALoader } from "three/examples/jsm/loaders/TGALoader";
import { App } from "../../application";
import { Point } from "../../renderExtension/types/Point";
import Const from "../../application/constants/Const";
import Events from "../../application/constants/Events";
import { BlobReader, BlobWriter, ZipReader } from "@zip.js/zip.js";
import MeshUtil from "../../renderExtension/utils/MeshUtil";
import { MTLLoader } from "three/examples/jsm/loaders/MTLLoader";
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
import MaterialAdapter from "../../renderExtension/Material/MaterialAdapter";
import ShaderConst from "../../renderExtension/Material/ShaderConst";
import AdjustHeight from "./utils/AdjustHeight";
import Helper from "../../utils/Helper";

/** 模型对象，支持gltf、glb、fbx、obj格式 */
class OverLay extends Group {
    private options: any = {
    };

    private loadingManager: LoadingManager;
    private explodeInterVal;
    private explodeNum;
    private mixer: AnimationMixer;
    private clock;
    private encrypted: boolean = false;
    private password: string = 'gisway';

    private debounceLoadEvent; //其他地方触发的加载事件，带防抖功能
    private everSetHeight = false; //是否设置过模型的高度
    private adjustHeightUtil: AdjustHeight;
    private static cacheModleMap: Map<string, { modelScene: Object3D, detailDataArray: Array<{ geometry: BufferGeometry, material: Material, animations }> }> = new Map<string, { modelScene: Object3D, detailDataArray: Array<{ geometry: BufferGeometry, material: Material, animations }> }>(); //url和model的映射关系，用于模型复用

    /**
     * 构造函数
     * @param options type:模型类型，可以不填，会自动根据后缀名识别 
     * url:模型地址 
     * position:模型WGS84经纬度坐标 
     * scale:模型缩放 
     * rotation:模型旋转 
     * success:加载成功后的回调函数
     * encrypted:模型是否是加密模式
     * password:加密模式下的模型密码
     * falldownSurface: 模型要贴到一个底图上，这个底图可以是3dtiles，也可以是其他的底图，比如dem
     * @example  
     * let model = new GE.OverLay({
        type: 'fbx',
        url: '/examples/resources/fbx/xqazm.FBX',
        position: [120.52950010876321, 30.268289949834518, 50],
        scale: [1, 1, 1],
        rotation: [0, 0, 0],
        success: function () {
            console.log('fbx loaded!');
        }
    });
    app.add(model);
     */
    constructor(options) {
        super();

        this.options = options;
        this.encrypted = (this.options.encrypted === undefined) ? this.encrypted : this.options.encrypted;
        this.password = (this.options.password === undefined) ? this.password : this.options.password;
        this['canNotSelect'] = options['canNotSelect'];

        let cacheModelData = OverLay.cacheModleMap.get(this.options.url);
        if (!cacheModelData) {
           this.loadNetModel(); //从网络加载模型
        } else {
            this.loadCacheModel(); //从缓存加载模型
        }

        this.clock = new Clock();
    }

    private loadNetModel(){ //从网络加载模型
        OverLay.cacheModleMap.set(this.options.url, { modelScene: null, detailDataArray: null });

        if (this.encrypted) {
            this.parse();
        }
        else {
            this.load();
        }
    }

    private loadCacheModel() {  //从缓存加载模型
        let cacheModelData = OverLay.cacheModleMap.get(this.options.url);
        let modelScene = cacheModelData.modelScene;//.clone(true);

        if (!modelScene) {
            let scope = this;
            setTimeout(() => {
                scope.loadCacheModel();
            }, 100);
            return;
        }

        modelScene = modelScene.clone(true);
        let detailDataArray = cacheModelData.detailDataArray;
        let index = 0;
        modelScene.traverse(function (child) {
            if (child instanceof Mesh) {
                let oldData = detailDataArray[index];
                child.geometry = oldData.geometry;
                child.material = oldData.material;

                index++;
            }
        })

        this.setModel(modelScene);
    }

    private async parse() {
        let response = await fetch(this.options.url);
        let fileBlob = await response.blob();
        const zipReader = new ZipReader(new BlobReader(fileBlob));
        const entries = await zipReader.getEntries();
        let decodedBlob: Blob = await entries[0].getData(new BlobWriter(), {
            password: this.password,
        });

        if (!this.options.type) {
            this.options.type = entries[0].filename.substring(entries[0].filename.lastIndexOf(".") + 1).toLowerCase();
        }
        let loader = this.getLoader();
        let url = URL.createObjectURL(decodedBlob);
        let scope = this;
        loader.load(url, function (object) {
            scope.setModel(object);
        });
    }

    private load() {
        //如果没有传type则自动截取文件后缀名为类型
        if (!this.options.type) {
            this.options.type = this.options.url.substring(this.options.url.lastIndexOf(".") + 1).toLowerCase();
        }
        let loader = this.getLoader();
        let mtlUrl = this.options.mtlUrl;
        if (mtlUrl) { //有mtl文件, 则先加载mtl文件，等mtl文件加载完毕再加载obj
            this.loadMtl(mtlUrl, loader);
        } else {
            this.loadModel(loader);
        }
    }

    private loadMtl(mtlUrl, loader) {
        let scope = this;
        let metLoader = new MTLLoader();
        metLoader.load(mtlUrl, function (materials) {
            materials.preload();

            (loader as OBJLoader).setMaterials(materials);
            scope.loadModel(loader);
        })
    }

    private loadModel(loader) {
        let scope = this;
        loader.loadAsync(this.options.url).then(function (object) {
            let modelScene = (object.scene ? object.scene : object);
            scope.setModel(modelScene);
        }).catch(function (error) {
            // 处理加载错误
            console.error(error);
        });
    }

    private setModel(modelScene) {
        let macrosList = new Array<string>();
        if (this.options.noTextureProject) {
            macrosList.push(ShaderConst.macrosConfig.exlcudeTextureProjectMafros);
        }
        if (this.options.isUseContourLine) { //使用等高线
            macrosList.push(ShaderConst.macrosConfig.contourLine);
        }

        MaterialAdapter.convertObj(modelScene, macrosList);

        this.saveModelToCache(modelScene);
        this.addObject(modelScene);
        this.setFalldown();

        //执行回调
        if (this.options.success) {
            this.options.success(this);
        }
    }

    private saveModelToCache(modelScene) {
        let modelData: { modelScene: Object3D, detailDataArray: Array<{ geometry: BufferGeometry, material: Material, animations }> } = {modelScene: null, detailDataArray: null};
        let detailDataArray: Array<{ geometry: BufferGeometry, material: Material, animations }> = new Array<{ geometry: BufferGeometry, material: Material, animations }>();
        modelScene.traverse(function (child) {
            if (child instanceof Mesh) {
                detailDataArray.push({ geometry: child.geometry, material: child.material, animations: child.animations });
            }
        });
        modelData.modelScene = modelScene;
        modelData.detailDataArray = detailDataArray;
        OverLay.cacheModleMap.set(this.options.url, modelData);
    }

    private addObject(object) {
        let scope = this;
        //自动播放动画
        if (object.animations && object.animations.length > 0) {
            scope.mixer = new AnimationMixer(object);
            const action = scope.mixer.clipAction(object.animations[0]);
            action.play();
            action.timeScale = this.options.actionTimeScale || 1;
            App.getInstance().on(Events.Render, scope.update, scope);
        }

        //根据配置项设置缩放 透明度 旋转
        if (scope.options.scale) {
            scope.scale.set(scope.options.scale[0], scope.options.scale[1], scope.options.scale[2]);
        }
        if (scope.options.opacity) {
            scope.setOpacity(scope.options.opacity);
        }

        if (scope.options.rotation) {
            object.rotateX((90 + scope.options.rotation[0]) / 180 * Math.PI);
            object.rotateY((scope.options.rotation[1]) / 180 * Math.PI);
            object.rotateZ((scope.options.rotation[2]) / 180 * Math.PI);
        }
        else if (!scope.options.gltfUpAxis || scope.options.gltfUpAxis == "Y") { //Y轴朝上
            scope.rotateX(90 / 180 * Math.PI);
        }
        // if(object.rotation.x != 0){
        // object.rotation.x = 0;

        // scope.rotateX(90 / 180 * Math.PI);
        // console.log(object.rotation);
        // console.log(this.options.url);
        // }
        scope.add(object);

        if(scope.options.position){
            //设置模型位置
            let position = new Point(scope.options.position[0], scope.options.position[1], scope.options.position[2], (scope.options.position[3] || Const.EPSGType.EPSG84)).toEPSGWeb();
            scope.position.set(position.x, position.y, position.z);
        }
        
        if(scope.options.isComputeBox){
            scope.computeBox(object)
        }

        //重置emissive通道
        if (scope.options.changeEmissive) {
            scope.changeEmissive();
        }
    }

    private computeBox(model){
           Helper.traverseObject(model, function (Object) {
                    if (Object.type == 'Mesh') {
                        let modelBox = new Box3().setFromObject(model, true);
                        Object[Const.MapConst.meshBox] = modelBox;
                    }
                    return true;
                });
    }

    private setFalldown() {
        if (this.options.falldownSurface) {
            let adjustHeightData = {mesh: this, falldownSurface: this.options.falldownSurface};
            this.adjustHeightUtil = new AdjustHeight(adjustHeightData);
            this.adjustHeightUtil.startAdjust();
        }
    }

    /**
     * 设置对象透明度
     * @param opacity 透明度
    */
    public setOpacity(opacity) {
        this.traverse((child) => {
            if (child instanceof Mesh) {
                child.material.opacity = opacity;
                child.material.needsUpdate = true;
            }
        })
    }

    /**
     * 用于修复fbx模型因为emissive错误读取引起的变黑问题
     */
    public changeEmissive() {
        this.traverse(function (obj) {
            if (obj.type == 'Mesh' && obj['material']) {
                if (Array.isArray(obj['material'])) {
                    for (let i = 0; i < obj['material'].length; i++) {
                        obj['material'][i].emissive.set(1, 1, 1);
                        obj['material'][i].emissiveIntensity = 1;
                        obj['material'][i].emissiveMap = obj['material'][i].map;
                    }
                }
                else {
                    obj['material'].emissive.set(1, 1, 1);
                    obj['material'].emissiveIntensity = 1;
                    obj['material'].emissiveMap = obj['material'].map;
                }
            }
        });
    }


    /**
     * 模型拆解
     * @param num 拆解距离
     * @param duration 模型拆解时间
     * @returns 
    */
    public explode(num: number = 10, duration: number = 0) {
        this.explodeNum = num;
        if (duration == 0) {
            this.explodeTo(num);
            return;
        }

        if (duration < 0) {
            console.warn('爆炸时间不能小于0');
            return;
        }

        let count = 0;
        const segement = duration / 100.0;
        let explodeInterVal = setInterval(() => {
            count++;
            if (count > segement) {
                clearInterval(this.explodeInterVal);
                this.explodeInterVal = null;
            }
            let currentNum = (num / segement) * count;
            this.explodeNum = currentNum;
            this.explodeTo(currentNum);

        }, segement);

        this.explodeInterVal = explodeInterVal;
    }

    /**
     * 停止模型拆解
     * @param duration 停止模型拆解动画所需时间
     * @returns 
    */
    public stopExplode(duration) {
        if (this.explodeNum == 0)
            return;
        if (this.explodeInterVal) {
            clearInterval(this.explodeInterVal);
            this.explodeInterVal = null;
        }

        let count = 0;
        const segement = duration / 100.0;
        if (!duration || duration == 0) {
            this.explodeTo(0);
            return;
        }

        let explodeInterVal = setInterval(() => {
            count++;
            if (count > segement) {
                clearInterval(explodeInterVal);
                this.explodeNum = 0;
            }
            let currentNum = this.explodeNum - (this.explodeNum / segement) * count;
            this.explodeTo(currentNum);
        }, segement);

    }

    private explodeTo(num) {
        var childBox = new Box3();
        let self = this;
        this.traverse(function (child) {

            if (child instanceof Mesh) {
                //@ts-ignore
                if (!child.originPosition) {
                    childBox.setFromObject(child);
                    var childCenter = new Vector3().addVectors(childBox.max, childBox.min).multiplyScalar(0.5);
                    //@ts-ignore
                    child.originPosition = childCenter;
                }
                //@ts-ignore
                if (isNaN(child.originPosition.x)) return;
                //@ts-ignore
                child.childCenter = new Vector3().subVectors(child.originPosition, self.position).normalize();
                //保存初始坐标
                //@ts-ignore
                if (!(child instanceof Mesh) || !child.childCenter) return;
                //爆炸公式
                //@ts-ignore
                child.position.copy(child.childCenter).multiplyScalar(num);
            }
        });
    }

    private getLoader() {
        this.loadingManager = new LoadingManager();
        let tl = new TGALoader(this.loadingManager);

        //添加TGA材质解析器
        this.loadingManager.addHandler(/tga$/, tl);

        switch (this.options.type) {
            case 'fbx': {
                return new FBXLoader(this.loadingManager)
                // return new FBXLoader();
            }
            case 'gltf':
            case 'glb': {
                const dracoLoader = new DRACOLoader();
                let dracoUrl = this.options.dracoPluginUrl
                dracoLoader.setDecoderPath(dracoUrl);

                let loader = new GLTFLoader(this.loadingManager);
                loader.setDRACOLoader(dracoLoader);
                return loader;
            }
            case 'obj': {
                return new OBJLoader(this.loadingManager);
            }
            default: {
                return new FBXLoader(this.loadingManager);
                // return new FBXLoader();
            }
        }
    }

    private update(): void {
        const delta = this.clock.getDelta();
        if (this.mixer) {
            this.mixer.update(delta);
        }
        // if (this.modelAnimation) {
        //   this.modelAnimation.update(delta);
        // }
    }

    /**
     * 获取模型详细信息
     * @returns 模型信息
     */
    public getModelInfo(): {
        nodes,//节点信息
        materials,//材质信息
        animations,//动画数量
        size,//模型大小
        triangleCount,//三角形数量
        vertexCount,//顶点数量
        nodesCount,//节点数量
        materialCount//材质数量
    } {
        let model = this.children[0];

        let nodes = [];
        let materials = {};
        let animations = 0, size = 0, triangleCount = 0, vertexCount = 0, nodesCount = 0;

        animations = model.animations?.length;
        model.traverse((node) => {
            //@ts-ignore
            if (node.isMesh) {
                //记录节点信息，节点数量，顶点数量，三角形数量
                nodes.push(node.name);
                nodesCount++;
                vertexCount += (<Mesh>node).geometry.attributes.position.count;
                triangleCount += (<Mesh>node).geometry.index ? (<Mesh>node).geometry.index.count : (<Mesh>node).geometry.attributes.position.count / 3;

                let material = (<Mesh>node).material
                if (material) {
                    if (Array.isArray(material)) {
                        //@ts-ignore
                        for (let i = 0; i < material.length; i++) {
                            materials[material[i].uuid] = material[i].name;
                        }
                    }
                    else {
                        materials[material.uuid] = material.name;
                    }
                }
            }
        })
        let info = {
            nodes: nodes,
            materials: [],
            animations: animations,
            size: size,
            triangleCount: triangleCount,
            vertexCount: vertexCount,
            nodesCount: nodesCount,
            materialCount: 0
        }
        //计算材质数量
        info.materials = Object.values(materials);
        info.materialCount = info.materials.length;
        //计算模型尺寸
        var box = new Box3();
        box.setFromObject(model);
        if (box.max && box.min) {
            info.size = Math.sqrt(Math.pow(box.max.x - box.min.x, 2) + (Math.pow(box.max.y - box.min.y, 2)));
        }

        return info;
    }

    /**
     * 销毁模型
     */
    public dispose() {
        MeshUtil.dispose(this);
        App.getInstance().off(Events.Render, this.update, this);
    }

    /**
     * 删除一个模型的缓存
     * @param url 
     */
    public static removeCacheModel(url: string) {
        OverLay.cacheModleMap.delete(url);
    }

    /**
     * 清空所有模型的缓存
     */
    public static removeAllCacheModel() {
        OverLay.cacheModleMap.clear();
    }
}

export { OverLay }